#pragma once

#include <system_error>

#include <unistd.h>
#include <sys/epoll.h>
#include "Erro.hpp"
#include "Log.hpp"

class SharedEpollEvents
{
public:
    SharedEpollEvents(int size)
        : _size(size), _ptr(new epoll_event[_size], [](epoll_event *ptr)
                            { delete[] ptr; })
    {
    }

    struct epoll_event &operator[](int index)
    {
        return _ptr.get()[index];
    }

    const struct epoll_event &operator[](int index) const
    {
        return _ptr.get()[index];
    }

    struct epoll_event *get()
    {
        return _ptr.get();
    }

    int size() const
    {
        return _size;
    }

private:
    int _size;
    std::shared_ptr<struct epoll_event> _ptr;
};

class Epoll
{
private:
    static const int err_fd = -1;
    static const int size = 128;

public:
    Epoll(int event_size = size)
        : _epfd(err_fd), _evens_size(event_size), _evens(event_size)
    {
    }
    ~Epoll()
    {
        if (_epfd != err_fd)
            close(_epfd);
    }

    void Create()
    {
        _epfd = epoll_create(_evens_size);
        if (_epfd < 0)
        {
            try
            {
                throw std::system_error(errno, std::system_category());
            }
            catch (std::system_error &erro)
            {
                logMessage(FATAL, "epoll create fail: %s", erro.what());
                exit(EPOLL_CREATE_ERROR);
            }
        }

        logMessage(DEBUG, "epfd create succes: %d", _epfd);
    }

    bool addEvent(int fd, uint32_t event)
    {
        struct epoll_event ev;
        ev.data.fd = fd;
        ev.events = event;
        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
        return n == 0;
    }

    int Wait(int timeout)
    {
        int ready_num = epoll_wait(_epfd, _evens.get(), _evens_size, timeout);
        if (ready_num < 0)
        {
            try
            {
                throw std::system_error(errno, std::system_category());
            }
            catch (std::system_error &erro)
            {
                logMessage(WARNING, "epoll Wait fail: %d, %s", erro.code().value(), erro.what());
            }
        }

        return ready_num;
    }

    bool Control(int sock, int action, uint32_t event = 0)
    {
        if (action == EPOLL_CTL_MOD)
        {
            struct epoll_event ev;
            ev.data.fd = sock;
            ev.events = event;
            return !epoll_ctl(_epfd, action, sock, &ev);
        }
        else if (action == EPOLL_CTL_DEL)
        {
            return !epoll_ctl(_epfd, action, sock, nullptr);
        }
        return false;
    }

private:
    int _epfd;
    int _evens_size;

public:
    SharedEpollEvents _evens;
};